/*
 * ============================================================================
 * Licensed Materials - Property of IBM
 * Project  Zero
 *
 * (C) Copyright IBM Corp. 2007  All Rights Reserved.
 *
 * US Government Users Restricted Rights - Use, duplication or disclosure
 * restricted by GSA ADP Schedule Contract with IBM Corp.
 * ============================================================================
 * Copyright (c) 1999 - 2006 The PHP Group. All rights reserved.
 * ============================================================================
 */
package com.ibm.p8.engine.parser.core;
//////////////////////////////////////////////////////////////////
//                                                              //
//   This file was auto-generated by the LPG Eclipse Tooling.   //
//   It is safe to edit this file. It will not be overwritten.  //
//                                                              //
//////////////////////////////////////////////////////////////////
					
					
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Logger;

import org.jikes.lpg.runtime.ParseErrorCodes;

import com.ibm.p8.engine.core.FatalError;
import com.ibm.p8.engine.core.RuntimeInterpreter;
import com.ibm.p8.utilities.log.P8LogManager;
import com.ibm.phpj.logging.SAPILevel;
import com.ibm.phpj.logging.SAPIComponent;


/**
 * CharStream contains an array of characters as the input stream to be parsed. This may
 * be internally held as a byte array or a char array. The byte array is imported using
 * the default platform encoding unless one is specified.
 */
public class CharStream implements LexerSymbols, TokenKindMap, ParseErrorCodes {
	private int index = -1;
	private char[] inputChars;
	private int numberOfChars;
	private int line = -1;
	private int[] lineOffsets; // an array of end of line offsets
	private int newLength = 0;
	private static final int INITIAL = 4000;
	private String encoding = "";
	private ArrayList<Error> errors;
	private String fileName;
	
	private static final String DEFAULTENCODING = "UTF-8";
	private static final char EOF_CHAR = '\uffff';
	private static final Logger LOGGER = P8LogManager._instance.getLogger(SAPIComponent.SAPI);
	

	/**
	 * Internal class representing an error.
	 */
	public static class Error {
		private int errorCode;
		private String locationInfo;
		private int leftToken;
		private int rightToken;
		private String tokenText;
		
		/**
		 * Constructor.
		 * 
		 * @param errorCode .
		 * @param locationInfo .
		 * @param leftToken .
		 * @param rightToken .
		 * @param tokenText .
		 */
		public Error(int errorCode, String locationInfo, int leftToken, int rightToken, String tokenText) {
			this.errorCode = errorCode;
			this.locationInfo = locationInfo;
			this.leftToken = leftToken;
			this.rightToken = rightToken;
			this.tokenText = tokenText;
		}
		
		public int getErrorCode() {
			return this.errorCode;
		}
		
		public String getLocationInfo() {
			return this.locationInfo;
		}
		
		public int getLeftToken() {
			return this.leftToken;
		}
		
		public int getRightToken() {
			return this.rightToken;
		}
		
		public String getTokenText() {
			return this.tokenText;
		}
		
	}

	/**
	 * Construct a stream with no contents.
	 * @param runtime The PHP runtime.
	 * @param filename The name of the file that the char stream will be obtained from.
	 */
	CharStream(RuntimeInterpreter runtime, String filename) {
		errors = new ArrayList<Error>();
		fileName = filename;
		index = -1; // ensure the start index is 0
		setLineOffset(0); // initialize line offset array
		if (runtime != null && runtime.getRuntimeManager() != null) {
			encoding = runtime.getRuntimeManager().getRuntimeServices().getConfigurationService().getStringProperty("PHP", "unicode.script_encoding");
		}
		
		if (encoding == "" || encoding == null) {
			encoding = DEFAULTENCODING; 
		}
		
		if (!Charset.isSupported(encoding)) {
			if (LOGGER.isLoggable(SAPILevel.SEVERE)) {
				LOGGER.log(SAPILevel.SEVERE , "2005");
			}	
			throw new FatalError("Unsupported or Invalid script encoding");						
		}
	}
	
	
    /**
     * Get the entire contents of the charstream.
     * @return char[] the stream
     */	
	public char[] getContents() {
		return inputChars;
	}

	/**
	 * Set the input stream contents.
	 * This will restart the stream with new contents.
	 * @param input - the stream char buffer.
	 */
	public void setContents(String input) {
		index = -1;
		setLineOffset(0); // initialize line offset array
		int length = input.length();
		inputChars = new char[length + 1];
		System.arraycopy(input.toCharArray(), 0, inputChars, 0, length);
		inputChars[length] = EOF_CHAR;
		numberOfChars = inputChars.length;
	}
	

	/**
	 * Set the byte array that this stream will use as its input.
	 * Uses the encoding specified when the stream was constructed to
	 * convert to java characters.
	 * @param input - the input bytes
	 * @throws java.io.IOException - if we can't read the byte array
	 */
	public void load(byte[] input) throws java.io.IOException {
		index = -1; // ensure the start index is 0
		setLineOffset(0); // initialize line offset array
		CharsetDecoder decoder = Charset.forName(encoding).newDecoder();
		/*
		 * Set up a decoder to convert from the script encoding into Java's
		 * internal form, specifying that any coding errors should simply be
		 * replaced with the default substitution character.
		 */
		decoder.onMalformedInput(CodingErrorAction.REPLACE);
		decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
		CharBuffer cB;
		try {
			cB = decoder.decode(ByteBuffer.wrap(input));
		} catch (CharacterCodingException e) {
			/*
			 * decode should never throw a Character Coding Exception because we
			 * specify the REPLACE error action
			 */
			if (LOGGER.isLoggable(SAPILevel.SEVERE)) {
				LOGGER.log(SAPILevel.SEVERE, "509");
			}
			throw new FatalError("unexpected Character Coding Exception.");
		}
		
		int limit = cB.limit();
		inputChars = new char[limit + 2];
		cB.get(inputChars, 0, limit);
		inputChars[limit] = EOF_CHAR;		
		numberOfChars = inputChars.length;
	}


	/**
	 * Gets a character and increments the index.
	 * @return the character.
	 */
	public char getChar() {
		
		if (index >= 0 && inputChars[index] == EOF_CHAR) {
			return EOF_CHAR;
		}
		
		index++;
		char c = inputChars[index];
		if (c == '\n') {
			setLineOffset(index + 1);
		}
		return c;
	}
	
	
	/**
	 * Peeks a character without altering the index.
	 * @param offset from the current index
	 * @return the character
	 */
	public char peekChar(int offset) {
		int position = index + offset;
		if (position >= numberOfChars) {
			return EOF_CHAR;
		} else {
			return inputChars[position];			
		}
	}

	/**
	 * Gets a character without altering the index.
	 * This is really peek, but not relative to the
	 * current index.
	 * @param offset from the base
	 * @return the character
	 */
	public char getChar(int offset) {
		if (offset >= numberOfChars) {
			return EOF_CHAR;
		}
		return inputChars[offset];
	}
	
	/**
	 * The current location in the stream.
	 * @return the index
	 */
	public int getIndex() {
		return index;
	}
	
	/**
	 * Move the current index.
	 * This is like fsetpos. If the resulting index is past the end of the stream, the index
	 * is set to the end of the stream. If it past the beginning of the stream, it is set to
	 * the beginning.
	 * @param offset from the current index
	 */
	public void moveIndex(int offset) {
		assert (offset >= 0);
		index = index + offset;
		if (index >= inputChars.length) {
			index = inputChars.length - 1;
		}		
		if (index < 0) {
			index = 0;
		}
	}

	/**
	 * Gets the line number at present index.
	 * @return the line number
	 */
	public int getLine() {
		return line;
	}

	/**
	 * Gets a line at a particular index.
	 * @param i the index to find the line number for.
	 * @return the line
	 */
	int getLineNumberOfCharAt(int i) {
		int lineNumber = Arrays.binarySearch(lineOffsets, i);
		return lineNumber < 0 ? -lineNumber - 1 : lineNumber < 1 ? 1 : lineNumber - 1;
	}

	/**
	 * Gets the column at a particular index.
	 * @param i the index to find the column number for.
	 * @return the column
	 */
	int getColumnOfCharAt(int i) {
		int lineNo = getLineNumberOfCharAt(i);
		return i - lineOffsets[lineNo - 1];
	}

	
	/**
	 * Alter a line offset.
	 * @param i The offset
	 */
	void setLineOffset(int i) {
		line++;
		if (line >= newLength) {
			reallocateLineOffsetsArray();
		}
		lineOffsets[line] = i;
	}

	/**
	 * Return the offset into the charstream that the specified
	 * line number begins at.
	 * @param lineNumber The line number to get the offset for.
	 * @return The offset for the specified line number.
	 */
	public int getLineOffset(int lineNumber) {
		if (lineNumber >= newLength) {
			return 0;
		}		
		return lineOffsets[lineNumber];
	}

	/**
	 * Modify the line offsets array.
	 *
	 */
	void reallocateLineOffsetsArray() {
		int oldLength = (lineOffsets == null ? 0 : newLength);

		if (oldLength == 0) {
			newLength = INITIAL;
			lineOffsets = new int[newLength];
		} else {
			newLength = oldLength + oldLength;
			System.arraycopy(lineOffsets, 0, lineOffsets = new int[newLength],
					0, oldLength);
		}
		Arrays.fill(lineOffsets, oldLength, newLength - 1, Integer.MAX_VALUE);
		return;
	}

	
	public String getFileName() {
		return fileName;
	}

	/**
	 * Report an error.
	 * @param errorCode the error number
	 * @param locationInfo where the error occurred
	 * @param leftToken The index of previous token
	 * @param rightToken The index of next token
	 * @param tokenText The text of the problem token
	 */
	public void reportError(int errorCode, String locationInfo, int leftToken,
			int rightToken, String tokenText) {
		errors.add(new Error(errorCode, locationInfo, leftToken, rightToken, tokenText));
	}

	public boolean isValidForParser() {
		return PHPParsersym.isValidForParser;
	};

	/**
	 * Deletes <code>len</code> characters from the char array containing the
	 * script we are parsing. Used primarily when parsing strings that
	 * contain escaped characters like \n,
	 * \t, \043 and \xA9
	 *  
	 * @param offset The position in the stream to start deleting chars.
	 * @param len The number of chars to delete.
	 */
	public void deleteChars(int offset, int len) {
		System.arraycopy(inputChars, offset + len, inputChars, offset,
				inputChars.length - offset - len);
	}
			
}

